/*******************************************************************************
 * Software Name : RCS IMS Stack
 *
 * Copyright (C) 2010-2016 Orange.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package com.gsma.rcs.core.ims.protocol.sip;

import javax2.sip.Transaction;
import javax2.sip.header.CallIdHeader;
import javax2.sip.message.Message;
import javax2.sip.message.Response;

/**
 * SIP transaction context object composed of a request and of the corresponding response. The
 * Transaction context is used for waiting responses of requests and also for waiting an ACK message
 * (special case).
 * 
 * @author JM. Auffret
 * @author yplo6403
 */
public class SipTransactionContext {

    /**
     * An interface to handle SIP provisional response
     */
    public interface INotifySipProvisionalResponse {
        public void handle180Ringing(SipResponse response);
    }

    /**
     * Transaction
     */
    private Transaction mTransaction;

    /**
     * Received message
     */
    private SipMessage recvMsg;

    /**
     * Callback to handle SIP provisional response
     */
    private INotifySipProvisionalResponse mCallbackSipProvisionalResponse;

    /**
     * Constructor
     * 
     * @param transaction SIP transaction
     */
    public SipTransactionContext(Transaction transaction) {
        this(transaction, null);
    }

    /**
     * Constructor
     * 
     * @param transaction SIP transaction
     * @param callback Callback to handle SIP provisional response
     */
    public SipTransactionContext(Transaction transaction, INotifySipProvisionalResponse callback) {
        mTransaction = transaction;
        mCallbackSipProvisionalResponse = callback;
    }

    /**
     * Get the SIP transaction
     * 
     * @return Transaction
     */
    public Transaction getTransaction() {
        return mTransaction;
    }

    /**
     * Get the SIP message that has been received
     * 
     * @return SIP message
     */
    public SipMessage getMessageReceived() {
        return recvMsg;
    }

    /**
     * Determine if a timeout has occurred
     * 
     * @return Returns True if there is a timeout else returns False
     */
    public boolean isTimeout() {
        return (recvMsg == null);
    }

    /**
     * Determine if the received message is a SIP response
     * 
     * @return Returns True if it's a SIP response else returns False
     */
    public boolean isSipResponse() {
        if (recvMsg != null) {
            return (recvMsg instanceof SipResponse);
        }
        return false;
    }

    /**
     * Determine if the received message is an intermediate response
     * 
     * @return Returns True if it's an intermediate response else returns False
     */
    public boolean isSipIntermediateResponse() {
        int code = getStatusCode();
        return (code < 200);
    }

    /**
     * Determine if the received message is a successful response
     * 
     * @return Returns True if it's a successful response else returns False
     */
    public boolean isSipSuccessfullResponse() {
        int code = getStatusCode();
        return (Response.OK <= code && Response.MULTIPLE_CHOICES > code);
    }

    /**
     * Determine if the received message is a SIP ACK
     * 
     * @return Returns True if it's a SIP ACK else returns False
     */
    public boolean isSipAck() {
        if (recvMsg != null) {
            SipRequest req = (SipRequest) recvMsg;
            if (req.getMethod().equals("ACK")) {
                return true;
            }
            return false;
        }
        return false;
    }

    /**
     * Get the SIP response that has been received
     * 
     * @return SIP response or null if it's not a response (e.g. ACK message)
     */
    public SipResponse getSipResponse() {
        if (isSipResponse()) {
            return (SipResponse) recvMsg;
        }
        return null;
    }

    /**
     * Get the status code of the received SIP response
     * 
     * @return Returns a status code or -1 if it's not a SIP response (e.g. ACK message)
     */
    public int getStatusCode() {
        int ret = -1;
        if (isSipResponse()) {
            SipResponse resp = (SipResponse) recvMsg;
            ret = resp.getStatusCode();
        }
        return ret;
    }

    /**
     * Get the reason phrase of the received SIP response
     * 
     * @return Returns a reason phrase or null if it's not a SIP response (e.g. ACK message)
     */
    public String getReasonPhrase() {
        String ret = null;
        SipResponse resp = getSipResponse();
        if (resp != null) {
            ret = resp.getReasonPhrase();
        }
        return ret;
    }

    /**
     * Wait the response of a request until a timeout occurs
     * 
     * @param timeout value in milliseconds
     */
    public void waitResponse(long timeout) {
        try {
            if (recvMsg != null) {
                // Response already received, no need to wait
                return;
            }
            synchronized (this) {
                super.wait(timeout);
            }
        } catch (InterruptedException e) {
            // Thread has been interrupted
            recvMsg = null;
        }
    }

    /**
     * A response has been received (SIP response or ACK or any other SIP message)
     * 
     * @param msg SIP message object
     */
    public void responseReceived(SipMessage msg) {
        synchronized (this) {
            recvMsg = msg;
            super.notify();
        }
    }

    /**
     * Reset transaction context
     */
    public void resetContext() {
        synchronized (this) {
            recvMsg = null;
            super.notify();
        }
    }

    /**
     * Get the transaction context ID associated a SIP message
     * 
     * @param msg SIP message
     * @return Transaction context ID
     */
    public static String getTransactionContextId(SipMessage msg) {
        return getTransactionContextId(msg.getStackMessage());
    }

    /**
     * Get the transaction context ID associated a SIP message
     * 
     * @param msg SIP message
     * @return Transaction context ID
     */
    public static String getTransactionContextId(Message msg) {
        CallIdHeader header = (CallIdHeader) msg.getHeader(CallIdHeader.NAME);
        return header.getCallId();
    }

    /**
     * Get the callback to handle provisional SIP response
     * 
     * @return the callback
     */
    public INotifySipProvisionalResponse getCallbackSipProvisionalResponse() {
        return mCallbackSipProvisionalResponse;
    }

}
